Conversation
…omponents Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Scaffolds the ObjectStack Mobile runtime baseline using Expo Router navigation, NativeWind/Tailwind styling with CSS-variable tokens, and a small shadcn-style UI primitive set to support metadata-driven UI rendering.
Changes:
- Adds Expo Router app structure with root providers (SafeArea + React Query) and a 4-tab navigation layout.
- Introduces token-based theming (
global.css+tailwind.config.js) and NativeWind/Metro/Babel wiring. - Adds initial UI primitives (
Button,Card,Input) plus a dashboard screen that renders cards from JSON-like metadata.
Reviewed changes
Copilot reviewed 16 out of 25 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| tsconfig.json | Enables strict TS and ~/ path alias; includes NativeWind + Expo env type files. |
| tailwind.config.js | Sets up NativeWind preset and semantic colors backed by CSS variables. |
| package.json | Adds Expo/Router/NativeWind/Query/Zustand + supporting deps and scripts. |
| nativewind-env.d.ts | Adds NativeWind type references. |
| metro.config.js | Wires NativeWind Metro integration with global.css as input. |
| lib/utils.ts | Adds cn() helper (clsx + tailwind-merge). |
| global.css | Defines light/dark design tokens as CSS variables. |
| components/ui/Input.tsx | Adds token-styled TextInput with focus ring and className support. |
| components/ui/Card.tsx | Adds composable Card primitives (header/title/content/etc.). |
| components/ui/Button.tsx | Adds Pressable-based button variants/sizes with haptics and className support. |
| babel.config.js | Configures Expo Babel preset + NativeWind Babel integration. |
| assets/splash-icon.png | Adds splash image asset. |
| assets/icon.png | Adds app icon asset. |
| assets/favicon.png | Adds web favicon asset. |
| assets/adaptive-icon.png | Adds Android adaptive icon asset. |
| app/_layout.tsx | Root layout: imports global CSS and sets up QueryClientProvider + SafeAreaProvider + Stack. |
| app/(tabs)/profile.tsx | Adds Profile tab placeholder UI using Button primitives. |
| app/(tabs)/notifications.tsx | Adds Notifications tab placeholder UI. |
| app/(tabs)/index.tsx | Adds Dashboard tab rendering cards from metadata (JSON-like). |
| app/(tabs)/apps.tsx | Adds Apps tab placeholder UI. |
| app/(tabs)/_layout.tsx | Configures bottom tabs and header/tab bar appearance. |
| app.json | Sets Expo app metadata, icons/splash, new arch flag, and expo-router plugin. |
| README.md | Documents stack, structure, and basic getting-started instructions. |
| .gitignore | Adds standard Node/Expo/native ignore patterns. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| export interface ButtonProps extends PressableProps { | ||
| variant?: keyof typeof buttonVariants; | ||
| size?: keyof typeof buttonSizes; | ||
| className?: string; | ||
| textClassName?: string; | ||
| children: React.ReactNode; | ||
| style?: ViewStyle; | ||
| } |
There was a problem hiding this comment.
ButtonProps re-declares style?: ViewStyle, which narrows/incompatibly overrides PressableProps['style'] (which supports StyleProp<ViewStyle> and functional styles). This can cause TS errors for callers passing style arrays or a style callback. Remove the custom style field or type it as PressableProps['style'] / StyleProp<ViewStyle> to match React Native’s API.
| "h-12 text-base text-foreground placeholder:text-muted-foreground", | ||
| className | ||
| )} | ||
| placeholderTextColor="#94a3b8" |
There was a problem hiding this comment.
placeholderTextColor is hard-coded to #94a3b8, which bypasses your token system and also conflicts with the placeholder:text-muted-foreground class. This will make the placeholder color incorrect in light mode (and harder to theme). Prefer deriving the placeholder color from tokens / theme (or remove the hard-coded prop and rely on one approach consistently).
| placeholderTextColor="#94a3b8" |
| headerStyle: { backgroundColor: "#ffffff" }, | ||
| headerTitleStyle: { fontWeight: "700", fontSize: 17 }, | ||
| headerShadowVisible: false, | ||
| tabBarActiveTintColor: "#1e40af", | ||
| tabBarInactiveTintColor: "#94a3b8", | ||
| tabBarStyle: { | ||
| borderTopColor: "#e2e8f0", | ||
| backgroundColor: "#ffffff", | ||
| }, |
There was a problem hiding this comment.
Tab/header colors are hard-coded hex values (#ffffff, #1e40af, #94a3b8, #e2e8f0). This bypasses the CSS-variable design tokens and will look wrong once dark mode is enabled. Consider sourcing these from the theme (e.g., via token-backed colors or a color-scheme switch) so the navigation chrome stays in sync with global.css tokens.
| return metadata.map((item, index) => { | ||
| switch (item.type) { | ||
| case "card": | ||
| return <MetadataCardRenderer key={index} meta={item} />; |
There was a problem hiding this comment.
renderFromMetadata uses key={index}. If this metadata becomes dynamic (insert/reorder/remove), index keys can lead to incorrect component reuse and UI state bugs. Prefer a stable key from the metadata (e.g., add an id field, or use a unique title if guaranteed unique).
| return metadata.map((item, index) => { | |
| switch (item.type) { | |
| case "card": | |
| return <MetadataCardRenderer key={index} meta={item} />; | |
| return metadata.map((item) => { | |
| switch (item.type) { | |
| case "card": | |
| return <MetadataCardRenderer key={item.title} meta={item} />; |
Enterprise low-code mobile runtime scaffold — an app container that dynamically renders UI from JSON metadata, similar to Salesforce Mobile or Workday.
Stack
Design System
CSS-variable token system in
global.csswith semantic Tailwind mappings (primary,card,muted,destructive, etc.). Enterprise palette: slate/gray base, deep blue primary (#1e40aflight /#60a5fbdark).UI Components (
components/ui/)shadcn/ui pattern —
cn()utility viaclsx+tailwind-merge,classNameprops, zeroStyleSheet.create:default | destructive | outline | ghost, sizes:sm | default | lg, haptic on pressCard,CardHeader,CardTitle,CardDescription,CardContent,CardFooterMetadata-Driven Rendering
Home screen demonstrates the core pattern — dynamic UI from JSON metadata:
File Structure
Warning
Firewall rules blocked me from connecting to one or more addresses (expand for details)
I tried to connect to the following addresses, but was blocked by firewall rules:
api.expo.dev/home/REDACTED/work/_temp/ghcca-node/node/bin/node node /home/REDACTED/work/mobile/mobile/node_modules/.bin/expo export --platform web --output-dir /tmp/expo-build modules/@npmcli/run-script/lib/node-gyp-bin/node(dns block)cdp.expo.dev/home/REDACTED/work/_temp/ghcca-node/node/bin/node node /home/REDACTED/.npm/_npx/956a30255ff895e3/node_modules/.bin/create-expo-app . --template blank-typescript --no-install(dns block)/home/REDACTED/work/_temp/ghcca-node/node/bin/node node /home/REDACTED/work/mobile/mobile/node_modules/.bin/expo export --platform web --output-dir /tmp/expo-build modules/@npmcli/run-script/lib/node-gyp-bin/node(dns block)/opt/hostedtoolcache/node/24.13.0/x64/bin/node /opt/hostedtoolcache/node/24.13.0/x64/bin/node /home/REDACTED/work/mobile/mobile/node_modules/expo/node_modules/@expo/cli/build/src/utils/telemetry/clients/flushFetchDetached.js /tmp/2458ece080fef03ae2cb36950afd3281/expo-telemetry.json(dns block)If you need me to access, download, or install something from one of these locations, you can either:
💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.